
package com.jf.cloud.product.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.jf.cloud.api.order.constant.OrderStatus;
import com.jf.cloud.api.order.feign.OrderFeignClient;
import com.jf.cloud.api.product.dto.SkuStockLockDTO;
import com.jf.cloud.common.cache.constant.CacheNames;
import com.jf.cloud.common.cache.constant.LockCacheNames;
import com.jf.cloud.common.cache.constant.ProductCacheNames;
import com.jf.cloud.common.cache.util.RedisUtil;
import com.jf.cloud.common.database.dto.PageDTO;
import com.jf.cloud.common.database.util.PageUtil;
import com.jf.cloud.common.database.vo.PageVO;
import com.jf.cloud.common.exception.LuckException;
import com.jf.cloud.common.order.vo.OrderItemVO;
import com.jf.cloud.common.order.vo.OrderVO;
import com.jf.cloud.common.product.constant.StockBillType;
import com.jf.cloud.common.product.constant.StockType;
import com.jf.cloud.common.response.ResponseEnum;
import com.jf.cloud.common.response.ServerResponseEntity;
import com.jf.cloud.common.rocketmq.config.RocketMqConstant;
import com.jf.cloud.common.util.BooleanUtil;
import com.jf.cloud.product.bo.SkuWithStockBO;
import com.jf.cloud.product.bo.SkuZoneInfoBO;
import com.jf.cloud.product.mapper.SkuStockLockMapper;
import com.jf.cloud.product.mapper.SkuStockMapper;
import com.jf.cloud.product.mapper.SkuStockZoneMapper;
import com.jf.cloud.product.mapper.SpuExtensionMapper;
import com.jf.cloud.product.model.SkuStockLock;
import com.jf.cloud.product.service.SkuStockLockService;
import com.jf.cloud.product.service.SkuStockService;
import com.jf.cloud.product.service.SkuStockZoneService;
import com.jf.cloud.product.service.StockBillLogService;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 库存锁定信息
 *
 * @author zz
 * @date 2020-12-22 16:12:10
 */
@Service
public class SkuStockLockServiceImpl implements SkuStockLockService {
    private final Logger logger = LoggerFactory.getLogger(SkuStockLockServiceImpl.class);


    @Autowired
    private SkuStockLockMapper skuStockLockMapper;
    @Autowired
    private SpuExtensionMapper spuExtensionMapper;
    @Autowired
    private SkuStockMapper skuStockMapper;
    @Autowired
    private OrderFeignClient orderFeignClient;
    @Autowired
    private RocketMQTemplate stockMqTemplate;
    @Autowired
    private StockBillLogService stockBillLogService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private SkuStockService skuStockService;
    @Autowired
    private SkuStockZoneMapper skuStockZoneMapper;
    @Autowired
    private SkuStockZoneService skuStockZoneService;

    @Override
    public PageVO<SkuStockLock> page(PageDTO pageDTO) {
        return PageUtil.doPage(pageDTO, () -> skuStockLockMapper.list());
    }

    @Override
    public SkuStockLock getById(Long id) {
        return skuStockLockMapper.getById(id);
    }

    @Override
    public void save(SkuStockLock skuStockLock) {
        skuStockLockMapper.save(skuStockLock);
    }

    @Override
    public void update(SkuStockLock skuStockLock) {
        skuStockLockMapper.update(skuStockLock);
    }

    @Override
    public void deleteById(Long id) {
        skuStockLockMapper.deleteById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ServerResponseEntity<Void> lock(List<SkuStockLockDTO> skuStockLocksParam) {
        List<SkuStockLock> skuStockLocks = new ArrayList<>();
        for (SkuStockLockDTO skuStockLockDTO : skuStockLocksParam) {
            SkuStockLock skuStockLock = new SkuStockLock();
            skuStockLock.setCount(skuStockLockDTO.getCount());
            skuStockLock.setOrderId(skuStockLockDTO.getOrderId());
            skuStockLock.setSkuId(skuStockLockDTO.getSkuId());
            skuStockLock.setSpuId(skuStockLockDTO.getSpuId());
            skuStockLock.setStatus(0);
            skuStockLock.setShopSpuId(Objects.isNull(skuStockLockDTO.getShopSpuId()) ? 0L : skuStockLockDTO.getShopSpuId());

            RLock lock = redissonClient.getLock(LockCacheNames.LOCK_ZONE_PREFIX + skuStockLockDTO.getSkuId());

            try{
                lock.lock();
                // 获取sku是否有分片
                SkuZoneInfoBO zoneInfo = skuStockService.getZoneInfo(skuStockLock.getSkuId());
                // 如果获取到sku的分片缓存为null，就刷新下
                if (Objects.isNull(zoneInfo)) {
                    RedisUtil.del(ProductCacheNames.SKU_ZONE_INFO + CacheNames.UNION + skuStockLock.getSkuId());
                    zoneInfo = skuStockService.getZoneInfo(skuStockLock.getSkuId());
                }
                logger.info("sku{}库存分片情况{}", skuStockLock.getSkuId(), zoneInfo.getHasZone());
                if (!BooleanUtil.isTrue(zoneInfo.getHasZone())) {
                    logger.info("未分片扣减库存"+ skuStockLock.getSkuId());
                    // 未分片用老方法扣减库存
                    reduceStockUnZone(skuStockLockDTO);
                } else {
                    logger.info("通过分片扣减库存"+ skuStockLock.getSkuId());
                    // 通过分片扣减库存
                    reduceStockByZone(skuStockLockDTO.getSkuId(),zoneInfo.getLimitZone(),skuStockLockDTO.getCount(),skuStockLockDTO.getSpuId(), skuStockLock);
                }
            }finally {
                lock.unlock();
            }

            // 第一次循环时，将库存锁定信息添加到集合中
            if (skuStockLocks.size() == 0){
                logger.info("第一次循环时，将库存锁定信息添加到集合中");
                skuStockLocks.add(skuStockLock);
                continue;
            }
            // 判断skuId是否已经存在，如果不存在，就添加到集合中
            List<Long> skuIdList = skuStockLocks.stream().map(SkuStockLock::getSkuId).collect(Collectors.toList());
            if (!skuIdList.contains(skuStockLock.getSkuId())){
                logger.info("库存锁定信息集合中skuId不重复，将库存锁定信息添加到集合中");
                skuStockLocks.add(skuStockLock);
                continue;
            }

            for (SkuStockLock stockLock : skuStockLocks) {
                // 如果skuId已经存在，就把数量累加
                if (Objects.equals(stockLock.getSkuId(), skuStockLock.getSkuId())) {
                    logger.info("库存锁定信息集合中skuId重复，将库存锁定信息数量累加");
                    stockLock.setCount(stockLock.getCount() + skuStockLock.getCount());
                }
            }

        }
        // 保存库存锁定信息
        skuStockLockMapper.saveBatch(skuStockLocks);

        List<Long> orderIds = skuStockLocksParam.stream().map(SkuStockLockDTO::getOrderId).collect(Collectors.toList());

        // 一个小时后解锁库存
        SendStatus sendStatus = stockMqTemplate.syncSend(RocketMqConstant.STOCK_UNLOCK_TOPIC, new GenericMessage<>(orderIds), RocketMqConstant.TIMEOUT, RocketMqConstant.CANCEL_ORDER_DELAY_LEVEL + 1).getSendStatus();
        if (!Objects.equals(sendStatus,SendStatus.SEND_OK)) {
            // 消息发不出去就抛异常，发的出去无所谓
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        logger.info("lock sku stock end....");
        return ServerResponseEntity.success();
    }

    /**
     * 通过分片扣减库存
     *
     * @param skuId skuId
     * @param availableZone 预计可用分片数量
     * @param count  扣减数量
     */
    private void reduceStockByZone(Long skuId, Integer availableZone,Integer count, Long spuId, SkuStockLock skuStockLock) {
        // 通过分片扣减库存，为啥要通过分片扣减库存呢？因为直接扣减库存会有行锁，也就是一个商品sku的并发会受到这个行锁的影响
        // 如果把一个行锁分成10行，更加细粒度，那么是不是就可以提高性能了呢
        // 可以参考考拉海淘的实现 https://zhuanlan.zhihu.com/p/45090981

        // 1. 通过次分片扣减
        if (availableZone > 0) {
            // 通过次分片可以扣减成功
            if (reduceStockByLessZone(skuId,availableZone,count, skuStockLock)){
                return;
            }
        }
        // 2. 次分片没有库存，或者 次分片无法扣减成功，通过主分片扣减库存
        int skuStockUpdateIsSuccess = skuStockMapper.reduceStockByOrder(skuId, count);
        if (skuStockUpdateIsSuccess == 1) {
            return;
        }
        // 3. 聚合扣减库存
        // 统计所有库存数量还有多少
        Integer mainStock = skuStockMapper.getStock(skuId);

        Integer sumLessZone = skuStockZoneMapper.getStockBySkuId(skuId);

        int skuTotalStock = mainStock + sumLessZone;
        // 库存真的不够扣减了
        if (skuTotalStock < count) {
            throw new LuckException(ResponseEnum.NOT_STOCK, skuId);
        }

        // 剩余库存
        int lastStock = skuTotalStock - count;
        // 重新分片
        // 库存小于200不用重新分片
        skuStockZoneService.redoZone(skuId, lastStock, spuId);
    }


    /**
     * 通过次分片扣减库存
     * @param skuId skuId
     * @param availableZone 预计可用分片数量
     * @param count  扣减数量
     * @return 是否成功
     */
    private boolean reduceStockByLessZone(Long skuId, Integer availableZone,Integer count, SkuStockLock skuStockLock) {
        Random random = new Random();
        int zoneNo = random.nextInt(availableZone);
        boolean updateSuccess = skuStockZoneMapper.reduceStockByOrder(skuId, zoneNo, count) >0;
        if (updateSuccess) {
            // 记录扣除分片的id，回调解锁的时候找到这个分片去扣锁定库存
            skuStockLock.setZoneNo(zoneNo);
            return true;
        }
        // 是不是一个库存都没办法扣除了
        if (!Objects.equals(count, 1)) {
            return false;
        }

        // 一个库存都扣减不过来了，说明已经没有库存了,也就是当前这个zoneNo的库存为0了
        // 那么这个分片就没啥用了，再随机过来也是白费
        // 此时要确定可用库存区域的
        Integer limitZoneStock;
        // 最后一个分片的库存有可能也为0，此时要找到真的最后一个不为0的分片
        // 比如说一共有8个分片，当前是3，就要开始查7 6 5 4有没有，没有的话，3也不用查了，因为就是这个没有才会进入这个方法，所以这个时候再查2 1 0就行了
        while ((limitZoneStock = skuStockZoneMapper.getStockByZoneLimit(skuId, availableZone-1)) == 0) {
            // 往前倒数一个
            availableZone -= 1;

            // 表示如果倒数到当前的话，可以跳过这次查询
            if (Objects.equals(availableZone-1,zoneNo)) {
                availableZone -= 1;
            }
            // 最后一个分片还是没有库存，就不用再查了
            if (availableZone == 0) {
                break;
            }
        }

        // 更新库存可用分片数量
        skuStockMapper.updateLimitZone(skuId, availableZone);

        // 所有的分片都没有库存了
        if (availableZone == 0) {
            return false;
        }

        // 可用库存的编号
        int availableZoneNo = availableZone - 1;
        // 还有可用的分片
        if (availableZoneNo > zoneNo) {
            // 扣完最后一个全部的库存
            skuStockZoneMapper.reduceStockByOrder(skuId, availableZoneNo, limitZoneStock);
            // 增加库存
            skuStockZoneMapper.addStock(skuId, availableZoneNo, limitZoneStock - count);
        } else {
            // 比如说一共有8个分片，当前是3，就要开始查7 6 5 4都没有库存，进入这个方法，把2 1 0的库存-1即可
            // 扣完最后一个全部的库存
            skuStockZoneMapper.reduceStockByOrder(skuId, availableZoneNo, count);
        }
        skuStockLock.setZoneNo(availableZoneNo);
        return true;
    }

    /**
     * 直接扣减库存
     * @param skuStockLockDTO
     */
    private void reduceStockUnZone(SkuStockLockDTO skuStockLockDTO) {
        // 减sku库存
        int skuStockUpdateIsSuccess = skuStockMapper.reduceStockByOrder(skuStockLockDTO.getSkuId(), skuStockLockDTO.getCount());

        if (skuStockUpdateIsSuccess < 1) {
            throw new LuckException(ResponseEnum.NOT_STOCK, skuStockLockDTO.getSkuId());
        }

        // 减商品库存
        int spuStockUpdateIsSuccess = spuExtensionMapper.reduceStockByOrder(skuStockLockDTO.getSpuId(), skuStockLockDTO.getCount());
        if (spuStockUpdateIsSuccess < 1) {
            throw new LuckException(ResponseEnum.NOT_STOCK, skuStockLockDTO.getSkuId());
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlockStock(List<Long> orderIds) {
        ServerResponseEntity<List<OrderVO>> ordersResponse = orderFeignClient.listOrderAndOrderItemByOrderIds(orderIds);
        if (!ordersResponse.isSuccess()) {
            throw new LuckException(ordersResponse.getMsg());
        }
        List<OrderVO> orderList = ordersResponse.getData();
        List<SkuStockLockDTO> skuStockLockList = new ArrayList<>();
        List<Long> needUnLockOrderIds = new ArrayList<>();
        for (OrderVO orderVO : orderList) {
            orderIds.remove(orderVO.getOrderId());
            // 该订单没有下单成功，或订单已取消，赶紧解锁库存
            if (orderVO.getStatus() == null || Objects.equals(orderVO.getStatus(), OrderStatus.CLOSE.value())) {
                needUnLockOrderIds.add(orderVO.getOrderId());
            }
            for (OrderItemVO orderItem : orderVO.getOrderItems()) {
                SkuStockLockDTO skuStockLockDTO = new SkuStockLockDTO();
                skuStockLockDTO.setCount(orderItem.getCount());
                skuStockLockDTO.setShopId(orderVO.getShopId());
                skuStockLockDTO.setOrderId(orderVO.getOrderId());
                skuStockLockDTO.setStatus(-1);
                skuStockLockDTO.setSkuId(orderItem.getSkuId());
                skuStockLockDTO.setSpuId(orderItem.getSpuId());
                skuStockLockList.add(skuStockLockDTO);
            }
        }
        if (CollectionUtil.isNotEmpty(orderIds)) {
            needUnLockOrderIds.addAll(orderIds);
        }
        if (CollectionUtil.isEmpty(needUnLockOrderIds)) {
            return;
        }

        List<SkuWithStockBO> allSkuWithStocks = skuStockLockMapper.listByOrderIds(needUnLockOrderIds);
        if (CollectionUtil.isEmpty(allSkuWithStocks)) {
            return;
        }
        List<Long> lockIds = allSkuWithStocks.stream().map(SkuWithStockBO::getId).collect(Collectors.toList());
        List<SkuWithStockBO> oldReturnStockList = new ArrayList<>();

        for (SkuWithStockBO skuStock : allSkuWithStocks) {

            RLock lock = redissonClient.getLock(LockCacheNames.LOCK_ZONE_PREFIX + skuStock.getSkuId());
            try{
                lock.lock();
                // 获取sku是否有分片
                SkuZoneInfoBO zoneInfo = skuStockService.getZoneInfo(skuStock.getSkuId());
                // 如果获取到sku的分片缓存为null，就刷新下
                if (Objects.isNull(zoneInfo)) {
                    RedisUtil.del(ProductCacheNames.SKU_ZONE_INFO + CacheNames.UNION + skuStock.getSkuId());
                    zoneInfo = skuStockService.getZoneInfo(skuStock.getSkuId());
                }
                if (!BooleanUtil.isTrue(zoneInfo.getHasZone())) {
                    // 未分片用老方法还原库存
                    // 还原商品库存
                    spuExtensionMapper.addStockByOrder(skuStock);
                    // 还原sku库存
                    skuStockMapper.addStockAndSubLockStockByOrder(skuStock);
                } else {
                    // 通过分片扣减锁定库存
                    subStockByZone(skuStock);
                }
            } finally {
                lock.unlock();
            }
        }

        // 将锁定状态标记为已解锁(删除锁定记录，不然就要分库分表了)
        int updateStatus = skuStockLockMapper.unLockByIds(lockIds);
        // 生成出入库明细信息
        stockBillLogService.generateStockLogOfOrderList(skuStockLockList, StockBillType.ORDER_CANCEL.value(), StockType.WAREHOUSING.value());

        if (updateStatus == 0) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    private void subStockByZone(SkuWithStockBO skuStock) {
        if (Objects.isNull(skuStock.getZoneNo())) {
            // 通过主分片扣除
            skuStockMapper.addStockAndSubLockStockByOrder(skuStock);
        } else {
            // 通过次分片扣除
            int updateStatus = skuStockZoneMapper.addStockByOrder(skuStock);
            if (updateStatus < 1) {
                // 更新失败 该分片库存为0 扣除这个分片的锁定库存 并且把库存加到主分片去
                skuStockZoneMapper.reduceLockStockByOrder(skuStock);
                skuStockMapper.addStockByOrder(skuStock);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void markerStockUse(List<Long> orderIds) {

        List<SkuWithStockBO> skuWithStocks = skuStockLockMapper.listByOrderIds(orderIds);

        //  ==============订单从正常状态变成已支付=============
        if (CollectionUtil.isNotEmpty(skuWithStocks)) {
            normalToPayed(orderIds, skuWithStocks);
        }

        // ================ 由于订单支付回调成功过慢，导致订单由取消变成已支付 ====================

        List<SkuWithStockBO> unLockSkuWithStocks = skuStockLockMapper.listUnLockByOrderIds(orderIds);

        if (CollectionUtil.isNotEmpty(unLockSkuWithStocks)) {
            cancelToPayed(orderIds, unLockSkuWithStocks);
        }
    }

    private void cancelToPayed(List<Long> orderIds, List<SkuWithStockBO> unLockSkuWithStocks) {
        List<SkuWithStockBO> skuWithStocks;
        List<SkuWithStockBO> zoneAddSaleList = new ArrayList<>();
        for (SkuWithStockBO unLockSkuWithStock : unLockSkuWithStocks) {
            Long skuId = unLockSkuWithStock.getSkuId();
            Integer count = unLockSkuWithStock.getCount();
            RLock lock = redissonClient.getLock(LockCacheNames.LOCK_ZONE_PREFIX + skuId);
            try {
                lock.lock();
                // 获取sku是否有分片
                SkuZoneInfoBO zoneInfo = skuStockService.getZoneInfo(skuId);
                // 如果获取到sku的分片缓存为null，就刷新下
                if (Objects.isNull(zoneInfo)) {
                    RedisUtil.del(ProductCacheNames.SKU_ZONE_INFO + CacheNames.UNION + skuId);
                    zoneInfo = skuStockService.getZoneInfo(skuId);
                }
                if (!BooleanUtil.isTrue(zoneInfo.getHasZone())) {
                    // 未分片用老方法扣减库存
                    // 减少商品实际库存，增加销量
                    spuExtensionMapper.reduceActualStockByCancelOrder(unLockSkuWithStock);
                    // 减少sku实际库存
                    skuStockMapper.reduceActualStockByCancelOrder(unLockSkuWithStock);
                } else {
                    // 通过分片扣减库存
                    zoneAddSaleList.add(unLockSkuWithStock);
                    // 有分片的情况先扣主分片 主分片不够扣就全部聚合查询 扣完进行重新分片
                    int updateStatus = skuStockMapper.reduceStockByCancelOrder(unLockSkuWithStock);
                    if (updateStatus < 1) {
                        // 主分片不够扣
                        // 统计所有库存数量还有多少
                        int skuTotalStock = skuStockService.getTotalBySkuId(skuId);
                        // 库存真的不够扣减了
                        if (skuTotalStock < count) {
                            throw new LuckException(ResponseEnum.NOT_STOCK, skuId);
                        }
                        // 剩余库存
                        int lastStock = skuTotalStock - count;
                        // 重新分片
                        // 库存小于200不用重新分片
                        skuStockZoneService.redoZone(skuId, lastStock, unLockSkuWithStock.getSpuId());
                    }
                }
            } finally {
                lock.unlock();
            }
        }

        if (CollUtil.isNotEmpty(zoneAddSaleList)) {
            // 分片后只需要增加商品销量
            spuExtensionMapper.addSaleNumByOrder(zoneAddSaleList);
        }

        // 增加商家代销销量
        skuWithStocks = unLockSkuWithStocks.stream().filter(skuWithStockBO -> !Objects.equals(skuWithStockBO.getShopSpuId(),0L)).collect(Collectors.toList());
        if(CollectionUtil.isNotEmpty(skuWithStocks)) {
            spuExtensionMapper.addShopSoldNumByOrder(skuWithStocks);
        }

        int updateStatus = skuStockLockMapper.markerStockUse(orderIds, -1);
        if (updateStatus == 0) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    private void normalToPayed(List<Long> orderIds, List<SkuWithStockBO> skuWithStocks) {
        List<SkuWithStockBO> zoneAddSaleList = new ArrayList<>();
        for (SkuWithStockBO skuStock: skuWithStocks) {
            RLock lock = redissonClient.getLock(LockCacheNames.LOCK_ZONE_PREFIX + skuStock.getSkuId());
            try{
                lock.lock();
                // 获取sku是否有分片
                SkuZoneInfoBO zoneInfo = skuStockService.getZoneInfo(skuStock.getSkuId());
                // 如果获取到sku的分片缓存为null，就刷新下
                if (Objects.isNull(zoneInfo)) {
                    RedisUtil.del(ProductCacheNames.SKU_ZONE_INFO + CacheNames.UNION + skuStock.getSkuId());
                    zoneInfo = skuStockService.getZoneInfo(skuStock.getSkuId());
                }
                if (!BooleanUtil.isTrue(zoneInfo.getHasZone())) {
                    // 未分片用老方法扣减库存
                    // 减少商品实际库存，增加销量
                    spuExtensionMapper.reduceActualStockByOrder(skuStock);
                    // 减少sku实际库存
                    skuStockMapper.reduceActualStockByOrder(skuStock);
                } else {
                    // 更新商品销量
                    zoneAddSaleList.add(skuStock);
                    // 通过分片扣减锁定库存，如果有记录分片id就去该分片扣，真实库存不做扣减，应该通过log去查询
                    if (Objects.nonNull(skuStock.getZoneNo())) {
                        skuStockZoneMapper.reduceLockStockByOrder(skuStock);
                    } else {
                        skuStockMapper.reduceLockStockByOrder(skuStock);
                    }
                }
            } finally {
                lock.unlock();
            }
        }
        if (CollUtil.isNotEmpty(zoneAddSaleList)) {
            // 增加商品销量
            spuExtensionMapper.addSaleNumByOrder(zoneAddSaleList);
        }
        // 增加商家代销销量
        skuWithStocks = skuWithStocks.stream().filter(skuWithStockBO -> !Objects.equals(skuWithStockBO.getShopSpuId(),0L)).collect(Collectors.toList());
        if(CollectionUtil.isNotEmpty(skuWithStocks)) {
            spuExtensionMapper.addShopSoldNumByOrder(skuWithStocks);
        }

        int updateStatus = skuStockLockMapper.markerStockUse(orderIds, 0);
        if (updateStatus == 0) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }
}
